package tuan.utils;

import java.util.*;

import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import tuan.travel.TravelCallback;

public class DomUtil {

	/**
	 * 前序遍历
	 * 
	 * @param sNode
	 * @param eNode
	 * @param tagName
	 * @return
	 * @throws Exception
	 */
	public static void postOrderTree(Node root, Node sNode, Node eNode, TravelCallback travel) throws Exception {
		Queue<Node> nodeQueue = new LinkedList<Node>();
		nodeQueue.add(sNode);
		while (nodeQueue.size() > 0) {
			Node node = nodeQueue.poll();
			if (node.equals(eNode)) {
				break;
			}
			travel.callback(root, node);
			if (node.hasChildNodes()) {
				while (node.hasChildNodes()) {
					node = node.getFirstChild();
				}
				nodeQueue.add(node);
			} else {
				while (node != null && !node.equals(root) && node.getNextSibling() == null) {
					node = node.getParentNode();
				}
				if (node != null && !node.equals(root) && node.getNextSibling() != null) {
					nodeQueue.add(node.getNextSibling());
				}
			}

		}
	}

	/**
	 * 获取节点集的最小公共祖先节点
	 * 
	 * @param nodeList
	 * @return 如无则返回null
	 */
	public static Node getMinComParrent(List<Node> nodeList) {
		Stack<Node> nodeStack = new Stack<Node>();
		for (Node node : nodeList) {
			nodeStack.push(node);
		}

		while (nodeStack.size() > 1) {
			Node node1 = nodeStack.pop();
			Node node2 = nodeStack.pop();
			boolean stop = false;

			while (node1 != null) {
				Node tmpNode2 = node2;
				while (tmpNode2 != null) {
					if (node1.equals(tmpNode2)) {
						nodeStack.push(node1);
						stop = true;
						break;
					}
					tmpNode2 = tmpNode2.getParentNode();
				}
				if (stop) {
					break;
				}
				node1 = node1.getParentNode();
			}
		}

		if (nodeStack.size() == 1) {
			return nodeStack.pop();
		} else {
			return null;
		}
	}

	/**
	 * 批量设置节点的文字属性
	 * 
	 * @param nodeList
	 */
	public static void setTextNodeFont(List<Node> nodeList) {
		Iterator<Node> iter = nodeList.iterator();
		while (iter.hasNext()) {
			Node node = iter.next();
			setTextNodeFont(node, null);
		}
	}

	/**
	 * 获取并设置文本节点的font属性 size,style,bBold,bItalic,color
	 * 
	 * @param node
	 */
	public static void setTextNodeFont(Node node, Node parent) {

		if (node.getNodeType() != Node.TEXT_NODE) {
			return;
		}
		if (node.getUserData("size") != null) {
			return;
		}

		Node textNode = node;// 文本节点
		Node parentNode = null;// 父节点，这里必须为 DIV/TR/HTML

		// 如果未指定parent，则自动计算其祖先节点，遇到 第一个DIV/TR/HTML终止
		if (parent != null) {
			parentNode = parent;
		} else {
			parentNode = getParent(node, new String[] { "TR", "DIV", "HTML" });
		}

		if (parentNode == null) {
			return;
		}

		// 属性初始化
		node.setUserData("size", -1, null);
		node.setUserData("color", "", null);
		node.setUserData("face", "", null);
		node.setUserData("bgcolor", "", null);
		node.setUserData("style", "", null);
		node.setUserData("bItalic", false, null);
		node.setUserData("bBold", false, null);

		// 从parentnode到textnode依次计算font属性
		textNode = textNode.getParentNode();
		while (textNode != null && !textNode.equals(parentNode.getParentNode())) {

			String tagName = textNode.getNodeName();

			// FONT
			if (tagName.equals("FONT")) {
				NamedNodeMap attrs = textNode.getAttributes();
				for (int i = 0; i < attrs.getLength(); i++) {
					Node attrNode = attrs.item(i);
					if ((Integer) node.getUserData("size") == -1 && attrNode.getNodeName().equalsIgnoreCase("size")) {
						node.setUserData("size", Integer.parseInt(attrNode.getNodeValue()), null);
					}
					if (attrNode.getNodeName().equalsIgnoreCase("color")) {
						node.setUserData("color", attrNode.getNodeValue(), null);
					}
					if (attrNode.getNodeName().equalsIgnoreCase("face")) {
						node.setUserData("face", attrNode.getNodeValue(), null);
					}
					if (attrNode.getNodeName().equalsIgnoreCase("bgcolor")) {
						node.setUserData("bgcolor", attrNode.getNodeValue(), null);
					}

					// 团购不考虑这种style，因为团购的文字多彩多样，只有新闻才需考虑
					/*
					 * if (node.getUserData("style").equals("") &&
					 * (attrNode.getNodeName().equalsIgnoreCase("id") ||
					 * attrNode.getNodeName().equalsIgnoreCase("class") ||
					 * attrNode.getNodeName().equalsIgnoreCase("style"))) {
					 * node.setUserData("style", attrNode.getNodeValue(), null);
					 * }
					 */
				}

				textNode = textNode.getParentNode();
				continue;
			}

			// EM
			if (tagName.equals("EM")) {
				node.setUserData("bItalic", true, null);

				textNode = textNode.getParentNode();
				continue;
			}

			// EM
			if (tagName.equals("B") || tagName.equals("STRONG")) {
				node.setUserData("bBold", true, null);

				textNode = textNode.getParentNode();
				continue;
			}

			// 其他
			NamedNodeMap attrs = textNode.getAttributes();
			for (int i = 0; i < attrs.getLength(); i++) {
				Node attrNode = attrs.item(i);
				if (attrNode.getNodeName().equalsIgnoreCase("bgcolor")) {
					node.setUserData("bgcolor", attrNode.getNodeValue(), null);
				}
				String attrName = attrNode.getNodeName();
				if (node.getUserData("style").equals("") && (attrName.equalsIgnoreCase("id") || attrName.equalsIgnoreCase("class") || attrName.equalsIgnoreCase("style"))) {
					// 对于SPAN里的文字不做style属性提取，误提率较大
					if (!(textNode.getNodeName().equals("SPAN") && attrName.equalsIgnoreCase("style"))) {
						node.setUserData("style", attrNode.getNodeValue(), null);
					}
				}
				String sizeMatchStr = "";
				if (attrNode.getNodeName().equals("style")) {
					sizeMatchStr = attrNode.getNodeValue();
				} else {
					sizeMatchStr = attrNode.getNodeName() + ":" + attrNode.getNodeValue();
				}
				if ((Integer) node.getUserData("size") == -1) {
					String[] styleEls = sizeMatchStr.split(";");
					for (int j = 0; j < styleEls.length; j++) {
						String styleEl = styleEls[j];
						String[] kv = styleEl.split(":");
						if (kv.length != 2) {
							continue;
						}
						if (kv[0].trim().equalsIgnoreCase("font-size")) {
							node.setUserData("size", RegUtil.toInteger(kv[1].trim()), null);
						}
					}
				}
			}

			textNode = textNode.getParentNode();
		}
	}

	/**
	 * 根据属性值过滤文本节点
	 * 
	 * @param nodeList
	 * @param attrNmae
	 * @return
	 */
	public static void filterByAttrValue(List<Node> nodeList, String attrName, Object attrValue) {
		Iterator<Node> nodeIter = nodeList.iterator();
		while (nodeIter.hasNext()) {
			Node node = nodeIter.next();
			Object value = node.getUserData(attrName);
			if (!attrValue.equals(value)) {
				nodeIter.remove();
			}
			if (value == null) {
				DomUtil.setTextNodeFont(node, null);
			}
		}
	}

	/**
	 * 获取属性最多节点数的文本节点的属性
	 * 
	 * @param nodeList
	 * @param attrNmae
	 * @return
	 */
	public static Object getMaxNodeAttr(List<Node> nodeList, String attrNmae) {
		Map<Object, Integer> counter = new HashMap<Object, Integer>();

		for (Node node : nodeList) {
			Object key = node.getUserData(attrNmae);
			if (!counter.containsKey(key)) {
				counter.put(key, 0);
			}
			counter.put(key, counter.get(key) + 1);
		}

		Set<Object> set = counter.keySet();
		int maxCount = 0;
		Object attr = null;
		for (Object key : set) {
			if (counter.get(key) > maxCount) {
				maxCount = counter.get(key);
				attr = key;
			}
		}
		return attr;
	}

	/**
	 * 获取包含字符数最多的文本节点属性
	 * 
	 * @param nodeList
	 * @param attrNmae
	 * @return
	 */
	public static Object getMaxTextAttr(List<Node> nodeList, String attrNmae) {
		Map<Object, Integer> counter = new HashMap<Object, Integer>();

		for (Node node : nodeList) {
			Object key = node.getUserData(attrNmae);
			if (!counter.containsKey(key)) {
				counter.put(key, 0);
			}
			counter.put(key, counter.get(key) + node.getTextContent().trim().length());
		}

		Set<Object> set = counter.keySet();
		int maxCount = 0;
		Object attr = null;
		for (Object key : set) {
			if (counter.get(key) > maxCount) {
				maxCount = counter.get(key);
				attr = key;
			}
		}
		// System.out.println(counter);
		return attr;
	}

	/**
	 * 根据父节点的tagname过滤节点
	 * 
	 * @param nodeList
	 * @param allowedParentTag
	 * @param notAllowedParentTag
	 */
	public static void filter(List<Node> nodeList, String[] allowedParentTag, String[] notAllowedParentTag) {
		Set<String> allowedTagSet = new HashSet<String>();
		Set<String> notAllowedTagSet = new HashSet<String>();

		if (allowedParentTag != null) {
			for (String s : allowedParentTag) {
				allowedTagSet.add(s);
			}
		}
		if (notAllowedParentTag != null) {
			for (String s : notAllowedParentTag) {
				notAllowedTagSet.add(s);
			}
		}
		Iterator<Node> iter = nodeList.iterator();
		while (iter.hasNext()) {
			Node node = iter.next();
			// allowedTag不为空，则优先考虑allowedTag过滤
			if (allowedTagSet.size() > 0) {
				if (allowedTagSet.contains(node.getParentNode().getNodeName())) {
					iter.remove();
				}
			} else {
				if (notAllowedTagSet.contains(node.getParentNode().getNodeName())) {
					iter.remove();
				}
			}
		}
	}

	/**
	 * 得到过滤后的文本节点
	 * 
	 * @param sNode
	 * @param eNode
	 * @param allowedParentTag
	 * @param notAllowedParentTag
	 * @return
	 */
	public static List<Node> getTextNode(Node sNode, Node eNode, String[] allowedParentTag, String[] notAllowedParentTag) {
		List<Node> nodeList = getNodeByTagName(sNode, eNode, "#text");
		filter(nodeList, allowedParentTag, notAllowedParentTag);
		return nodeList;
	}

	/**
	 * 根据tagname获取节点LIST
	 * 
	 * @param sNode
	 * @param eNode
	 * @param allowedTag
	 * @param notAllowedTag
	 * @return
	 */
	public static List<Node> getNodeByTagName(Node sNode, Node eNode, String tagName) {
		return getPostOrderNodeByTagName(sNode, eNode, tagName);
	}

	/**
	 * 前序遍历
	 * 
	 * @param sNode
	 * @param eNode
	 * @param tagName
	 * @return
	 */
	public static List<Node> getPreOrderNodeByTagName(Node sNode, Node eNode, String tagName) {
		List<Node> newNodeList = new ArrayList<Node>();
		Stack<Node> nodeStack = new Stack<Node>();

		nodeStack.push(getRootNode(sNode));
		boolean beginMatch = false;
		while (nodeStack.size() > 0) {
			Node node = nodeStack.pop();

			if (node.equals(sNode)) {
				beginMatch = true;
			}
			if (node.equals(eNode)) {
				break;
			}

			// 判断是否是指定tagname的节点
			if (beginMatch && node.getNodeName().equals(tagName)) {
				newNodeList.add(node);
			}
			NodeList subNodes = node.getChildNodes();
			for (int i = subNodes.getLength() - 1; i >= 0; i--) {
				Node subNode = subNodes.item(i);
				nodeStack.push(subNode);
				if (subNode.equals(eNode)) {
					break;
				}
			}
		}
		return newNodeList;
	}

	/**
	 * 后序遍历
	 * 
	 * @param sNode
	 * @param eNode
	 * @param tagName
	 * @return
	 */
	public static List<Node> getPostOrderNodeByTagName(Node sNode, Node eNode, String tagName) {
		List<Node> nodeList = new ArrayList<Node>();
		Stack<Node> nodeStack = new Stack<Node>();

		Node root = getRootNode(sNode);
		root.setUserData("postordertag", null, null);
		nodeStack.push(root);
		boolean beginMatch = false;
		while (nodeStack.size() > 0) {
			Node node = nodeStack.peek();
			Integer travelTag = (Integer) node.getUserData("postordertag");

			if (travelTag == null || travelTag == 0) {
				// 未做标记，先将其所有子孙节点放进栈中
				if (node.equals(sNode)) {
					beginMatch = true;
				}

				NodeList subNodes = node.getChildNodes();
				List<Node> cNodes = new ArrayList<Node>();
				for (int i = 0; i < subNodes.getLength(); i++) {
					Node subNode = subNodes.item(i);
					cNodes.add(subNode);
					if (subNode.equals(eNode)) {
						break;
					}
				}
				for (int i = cNodes.size() - 1; i >= 0; i--) {
					Node subNode = subNodes.item(i);
					subNode.setUserData("postordertag", null, null);// 置位，表示下次直接访问
					nodeStack.push(subNode);
				}
				node.setUserData("postordertag", 1, null);// 置位，表示下次直接访问
			} else {
				// 做过标记，访问该节点,并清除访问标识
				node = nodeStack.pop();
				// System.out.println(node.getNodeName());
				if (node.equals(eNode)) {
					break;
				}
				// 判断是否是指定tagname的节点
				if (beginMatch && node.getNodeName().equals(tagName)) {
					nodeList.add(node);
				}
				node.setUserData("postordertag", null, null);// 置位，表示下次直接访问
			}
		}
		return nodeList;
	}

	/**
	 * 对node按块聚类
	 * 
	 * @param nodeList
	 * @return
	 */
	public static List<List<Node>> runCluster(List<Node> nodeList) {
		List<List<Node>> nodeListList = new ArrayList<List<Node>>();
		int i = 0;
		int j = 0;
		while (i < nodeList.size()) {
			List<Node> cnodeList = new ArrayList<Node>();
			Node node1 = nodeList.get(i);
			cnodeList.add(node1);
			j = i + 1;
			while (j < nodeList.size()) {
				Node node2 = nodeList.get(j);
				NodeDistance nd = new NodeDistance(node1, node2);
				if (nd.sim == 1) {
					cnodeList.add(node2);
					j++;
				} else {
					break;
				}
			}
			i = j;
			nodeListList.add(cnodeList);
		}
		return nodeListList;
	}

	/**
	 * 获取节点指定tagname的第一个父节点
	 * 
	 * @param node
	 * @param parentTags
	 * @return
	 */
	public static Node getParent(Node node, String[] parentTags) {
		Node parentNode = node.getParentNode();
		Set<String> parentTagSet = new HashSet<String>();

		if (parentTags != null) {
			for (String s : parentTags) {
				parentTagSet.add(s);
			}
		}

		parentNode = parentNode.getParentNode();
		while (parentNode != null) {
			if (parentTagSet.contains(parentNode.getNodeName())) {
				break;
			}
			parentNode = parentNode.getParentNode();
		}
		return parentNode;
	}

	/**
	 * 获取到根节点的H标签，如果有的话
	 * 
	 * @param node
	 * @return
	 */
	public static String getHTagName(Node node) {
		while (node != null) {
			if (node.getNodeName().startsWith("H") && node.getNodeName().length() == 2) {
				return node.getNodeName();
			}
			node = node.getParentNode();
		}
		return "";
	}

	/**
	 * 获取纯文本长度，剔除空格、转移实体
	 * 
	 * @param text
	 * @return
	 */
	public static int getPureTextLength(String text) {
		return getPureText(text).length();
	}

	/**
	 * 获取纯文本长,剔除空格、转移实体
	 * 
	 * @param text
	 * @return
	 */
	public static String getPureText(String text) {
		text = text.replaceAll("[\\s]+", "");
		text = text.replaceAll("&[a-zA-Z]+;*", "");
		return text;
	}

	/**
	 * 调试用
	 * 
	 * @param node
	 * @param text
	 */
	@SuppressWarnings("unchecked")
	public static void addDebugInfo(Node node, String text) {
		List<String> arr = (ArrayList<String>) node.getUserData("debug");
		if (arr == null) {
			arr = new ArrayList<String>();
		}
		arr.add(text);
		node.setUserData("debug", arr, null);
	}

	/**
	 * 获取节点的指定属性size
	 * 
	 * @param node
	 * @param attrName
	 *            width，height
	 * @return
	 */
	public static int getNodeSize(Node node, String attrName) {
		String sizeMatchStr = "";
		NamedNodeMap attrs = node.getAttributes();
		for (int i = 0; i < attrs.getLength(); i++) {
			Node attrNode = attrs.item(i);
			if (attrNode.getNodeName().equals("style")) {
				sizeMatchStr = attrNode.getNodeValue();
			} else {
				sizeMatchStr = attrNode.getNodeName() + ":" + attrNode.getNodeValue();
			}
			if (!attrNode.getNodeName().equals(attrName)) {
				sizeMatchStr += ";" + attrNode.getNodeName() + ":" + attrNode.getNodeValue();
			}

			String[] styleEls = sizeMatchStr.split(";");
			for (int j = 0; j < styleEls.length; j++) {
				String styleEl = styleEls[j];
				String[] kv = styleEl.split(":");
				if (kv.length != 2) {
					continue;
				}
				if (kv[0].trim().equalsIgnoreCase(attrName)) {
					return RegUtil.toInteger(kv[1].trim());
				}
			}
		}
		return -1;
	}

	/**
	 * 获取根节点
	 * 
	 * @param node
	 * @return
	 */
	public static Node getRootNode(Node node) {
		while (node.getParentNode() != null) {
			node = node.getParentNode();
		}
		return node;
	}

	public static void debugPrint(Node node, String attrName) {
		if (node == null) {
			System.out.println("NULL");
			return;
		}
		if (node.getAttributes() == null) {
			System.out.println("NULL");
			return;
		}
		if (node.getAttributes().getNamedItem(attrName) == null) {
			System.out.println("NULL");
			return;
		}
		System.out.println(node.getAttributes().getNamedItem(attrName));
	}

	/**
	 * 获取节点下的文本子节点
	 * 
	 * @param node
	 * @return
	 */
	public static List<Node> getSubTextNode(Node node) {
		String[] notAllowedParentTag = new String[] { "OPTION",
				"SELECT",
				"SCRIPT",
				"CDATA",
				"STYLE",
				"TITLE",
				"HEAD" };
		Node sNode = node;
		Node eNode = node;
		while (eNode != null && eNode.getNextSibling() == null) {
			eNode = eNode.getParentNode();
		}
		if (eNode != null) {
			eNode = eNode.getNextSibling();
		}
		List<Node> nodeList = DomUtil.getTextNode(sNode, eNode, null, notAllowedParentTag);
		return nodeList;
	}

	/**
	 * 获取正文
	 * 
	 * @return
	 */
	public static String getContentText(Node node) {
		String text = "";
		String[] notAllowedParentTag = new String[] { "OPTION",
				"SELECT",
				"SCRIPT",
				"CDATA",
				"STYLE",
				"TITLE",
				"HEAD" };
		Node sNode = node;
		Node eNode = node;
		while (eNode != null && eNode.getNextSibling() == null) {
			eNode = eNode.getParentNode();
		}
		if (eNode != null) {
			eNode = eNode.getNextSibling();
		}
		List<Node> nodeList = DomUtil.getTextNode(sNode, eNode, null, notAllowedParentTag);
		Iterator<Node> iter = nodeList.iterator();
		Node node1 = null;
		if (iter.hasNext()) {
			node1 = iter.next();
			text += node1.getTextContent().trim();
		}
		while (iter.hasNext()) {
			Node node2 = iter.next();
			if (node2.getTextContent().trim().length() == 0) {
				continue;
			}
			Node cPrentNode = getMinComParrent(Arrays.asList(node1, node2));
			String tagName = cPrentNode.getNodeName();
			if (cPrentNode != null && tagName.equals("P") || tagName.equals("LI") || tagName.equals("SPAN") || tagName.equals("TR") || tagName.equals("TH")) {
				text += node2.getTextContent().trim();
			} else if (tagName.equals("TD")) {
				text += "&nbsp;" + node2.getTextContent().trim();
			} else {
				text += "<br>" + node2.getTextContent().trim();
			}
			node1 = node2;
		}
		return text;
	}

}

class NodeDistance {
	public Node node1;
	public Node node2;

	public int distance = Integer.MAX_VALUE;
	public int sim = 0;

	public NodeDistance(Node node1, Node node2) {
		this.node1 = node1;
		this.node2 = node2;
		compute();
	}

	void compute() {
		Node pNode = DomUtil.getMinComParrent(Arrays.asList(node1, node2));

		Node tmpNode1 = node1;
		Node tmpNode2 = node2;
		while (!tmpNode1.equals(pNode) && !tmpNode2.equals(pNode)) {
			tmpNode1 = tmpNode1.getParentNode();
			tmpNode2 = tmpNode2.getParentNode();
			distance++;
		}
		if (tmpNode1.equals(pNode) && tmpNode2.equals(pNode)) {
			sim = 1;
		}
		while (!tmpNode1.equals(pNode)) {
			tmpNode1 = tmpNode1.getParentNode();
			distance++;
		}
		while (!tmpNode2.equals(pNode)) {
			tmpNode2 = tmpNode2.getParentNode();
			distance++;
		}
	}

}
